require(tidyverse)
Warning message:
replacing previous import ‘flowViz::contour’ by ‘graphics::contour’ when loading ‘flowStats’ 
require(flowCore)
require(flowClust)
require(openCyto)
require(ggcyto)
require(cowplot)

Analysis

Import data and edit the meta data

import the data from the RDSS (just once) and then write it to the local disk

Simplify the sample names

# import the longest substring function from the PTXQC package (https://rdrr.io/cran/PTXQC/man/LCSn.html)
source("../../script/20220326-simplify-names-subroutine.R")
shortNames <- simplifyNames(oriNames) %>% 
  gsub(".fcs","",.)
sampleNames(fs) <- shortNames

Metadata

sample <- read.csv("20220803-sample-list.csv") %>% 
  column_to_rownames(var = "file")
pData(fs) <- sample

Gating

Next we use a series of plots to guide our gating strategy for identifying the population we want to work with. ### Remove outliers We first gate on FSC.H and SSC.H to remove outliers (events that are too big or too small). The Attune instrument we use can record six decades (100-106), with the first two decades mostly occupied by electronic noise.

Let’s first define a gate and visualize it in a plot before adding it to a GatingSet.

outlier.gate <- rectangleGate(filterId = "-outlier", "FSC.H" = c(1.2e5, 1e6), "SSC.H" = c(1e2, 1e6))
ggcyto(fs, aes(x = FSC.H, y = SSC.H), subset = "root") +
  geom_hex(bins = 64) + geom_gate(outlier.gate) + facet_wrap(~name, ncol = 9) + theme_bw()

Add this gate to the GatingSet

gs <- GatingSet(fs) # create a GatingSet
gs_pop_add(gs, outlier.gate, parent = "root")
[1] 2
recompute(gs)
done!

PHO5-mCherry induction

When I plotted the singlet events on GFP-RFP 2d space, I noticed a few samples that show more than one population of cells, where the main population appeared to be “induced” while one or more subpopulations are less or not induced. While the biological reasons behind require further investigation and may be very interesting (heterogeneity), for this analysis we will use flowClust to identify the main population and move forward.

ggcyto(gs, aes(x = BL1.H, y = YL2.H), subset = "-outlier") + geom_hex(bins = 64) +
  facet_wrap(~name, ncol = 9) + scale_x_logicle() + scale_y_logicle() + theme_bw()

Be careful when working with the GatingSet and GatingHierarchy objects – these are strictly reference classes, meaning that most of the operations work by pointers and the operations will change the underlying data. For example, the first line of the code below (commented out) obtains a pointer to the underlying data rather than making a copy of that data. any operations on it will change the original data as a result.

#ex <- gs_pop_get_data(gs, "singlet")[[1]]
ex <- fs[["NA-156-2"]]
#lgcl <- estimateLogicle(ex, channels = c("BL1.H", "YL2.H"))
lgcl <- logicleTransform("induction")
# set cluster gate parameters
k = 1; q = 0.9
# end setting
ex <- transform(ex, lgBL1.H = lgcl(`BL1.H`), lgYL2.H = lgcl(`YL2.H`))
fluo.gate <- gate_flowclust_2d(ex, "lgBL1.H", "lgYL2.H", K = k, quantile = q)
The prior specification has no effect when usePrior=no
Using the serial version of flowClust
ggcyto(ex, aes(x = lgBL1.H, y = lgYL2.H)) + geom_hex(bins = 64) + geom_gate(fluo.gate) + geom_stats()# + scale_x_logicle() + scale_y_logicle()

Even though flowClust is supposed to perform its own transformation (modified Box-Cox), empirically I found the clustering seem to work better on logicle transformed data for the two fluorescent channels. Therefore I’m transforming the underlying data of the GatingSet. Note that it seems to be difficult to “create new parameters” to store the transformed data, while keeping the original data intact. Instead, the transformation functions constructed using the constructor logicle_trans() stores the inverse transformation functions, which can be used to perform the inverse transformation when needed. Followed the manual for GatingSet here

lgcl <- logicle_trans()
transList <- transformerList(c(lgBL1.H = "BL1.H", lgclYL2.H = "YL2.H"), lgcl)
transform(gs, transList)
A GatingSet with 81 samples

to obtain the original data, use gs_pop_get_data(gs[[1]], inverse.transform = TRUE)

Now we can do the flowClust gating

dat <- gs_pop_get_data(gs, "-outlier") # get parent data
inductionGate <- fsApply(dat, function(fr)
  openCyto::gate_flowclust_2d(fr, "BL1.H", "YL2.H", K = k, quantile = q)
)
gs_pop_add(gs, inductionGate, parent = "-outlier", name = "induction")
[1] 3
recompute(gs)
ggcyto(gs, aes(x = BL1.H, y = YL2.H), subset = "-outlier") + geom_hex(bins = 64) + geom_gate("induction") + geom_stats() + 
  facet_wrap(~name, ncol = 9) + theme_bw()

Several samples didn’t work well with the clustering algorithm. Here we will use a 2 cluster gating strategy to select the expressing population.

list.redo <- c("188-555-3", "188-555-6", "241-555-1")
newGate <- lapply(list.redo, function(x){
  gate_flowclust_2d(dat[[x]], "BL1.H", "YL2.H", K = 2, quantile = 0.9, target = c(3,4))
})
names(newGate) <- list.redo
ggcyto(gs[list.redo], aes(x = BL1.H, y = YL2.H), subset = "-outlier") + geom_hex(bins = 64) +
  geom_gate(newGate) + theme_bw() #+ geom_stats()

update the inductionGate object

gs_pop_set_gate(gs[list.redo], "induction", newGate)
[[1]]
NULL

[[2]]
NULL

[[3]]
NULL
recompute(gs[list.redo], "induction")
done!

Check the results

ggcyto(gs, aes(x = BL1.H, y = YL2.H), subset = "-outlier") +
  geom_hex(bins = 64) + geom_gate("induction") + geom_stats() +
  facet_wrap(~name, ncol = 9) + theme_bw()

Normalization

Brian Metzger and colleagues proposed a simple correction in their 2015 paper. The intuition behind this method is that FSC is proportional to the max 2d projection (area) of a cell, and thus FSC^(3/2) should be roughly proportional to the volume. By contrast, the fluorescent channels should be the cumulative intensity from the whole cell. Therefore dividing FP intensity by FSC^(3/2) should effectively remove the variation due to cell size difference. He did say that the Wittkopp lab later switched to a different method based on PCA and rotations. After reading those papers, I thought the simpler one will suffice for us.

Test normalization formula

# get population data
fs.out <- gs_pop_get_data(gs, y = "induction", inverse.transform = TRUE) # get the inverse transformed data
# come up with an approximate FSC.H value for an average event to be used a scalar for the next step
mfsc <- 5e5 # based on the mode of the median of FSC.H from all samples
# fs.out is of the cytoframe class, which is a reference class. need to convert to flowframe for transformation
# https://www.bioconductor.org/packages/devel/bioc/vignettes/flowWorkspace/inst/doc/flowWorkspace-Introduction.html
exponent <- 1.5
# ex <- cytoframe_to_flowFrame(fs.out[["A9"]]) %>%
#   transform(nFSC = FSC.H/mfsc) %>% 
#   transform(nGFP = BL1.H/nFSC^(exponent), nRFP = YL2.H/nFSC^(exponent))

ex <- cytoset_to_flowSet(fs.out) %>% 
  transform(nFSC = FSC.H/mfsc) %>% 
  transform(nGFP = BL1.H/nFSC^(exponent), nRFP = YL2.H/nFSC^(exponent))

plot the examples

ggplot(ex, aes(x = nFSC, y = nRFP/1e3)) + geom_hex(bins = 64) + scale_y_log10() + stat_smooth(method = "lm") +
  scale_fill_gradientn(colours = rev(RColorBrewer::brewer.pal(11, "Spectral"))) + facet_wrap(~name, scales = "free_y") +
  theme_bw()

# calculate ratio
norm.data <- fsApply(fs.out, function(cf) {
  cf <- cbind(cf, 
              nRFP = cf[,"YL2.H"] / (cf[, "FSC.H"]/mfsc)^(exponent), 
              nGFP = cf[,"BL1.H"] / (cf[, "FSC.H"]/mfsc)^(exponent))
  apply(cf[, c("FSC.H", "BL1.H", "YL2.H", "nGFP", "nRFP")], 2, median)
  }, use.exprs = TRUE) %>% 
  as_tibble(rownames = "name")  %>% 
  mutate(across(BL1.H:nRFP, ~ round(.x, 1)))

Output

The goal is to export the gated events and calculate the RFP/GFP and take the median, which will be used in downstream analyses.

Get the population stats

stats <- gs_pop_get_stats(gs) %>% 
  as_tibble() %>% 
  mutate(pop = gsub(".*/", "", pop), pop = gsub("-outlier", "cells", pop)) %>% 
  pivot_wider(names_from = pop, names_prefix = "n_", values_from = count)

Export the data

# pull all info together in a single tibble
final <- left_join(as_tibble(pData(fs)), stats, by = c("name" = "sample")) %>% 
  left_join(norm.data, by = "name")
write_tsv(final, "20220803-gated-median-out.txt")
LS0tCnRpdGxlOiAiQ29sbGVjdCBmbG93IGRhdGEgZm9yIGNoaW1lcmljIGNvbnN0cnVjdHMgaW4gbmV3IGJhY2tncm91bmQgd2l0aCBzdGFuZGFyZGl6ZWQgcHJvdG9jb2wiCmF1dGhvcjogQmluIEhlCmRhdGU6ICIzIGFvdXQgMjAyMiAodXBkYXRlZCBgciBmb3JtYXQoU3lzLnRpbWUoKSwgJyVtLyVkLyV5JylgKSIKb3V0cHV0OgogIGh0bWxfbm90ZWJvb2s6CiAgICB0b2M6IHRydWUKICAgIHRvY19kZXB0aDogNAogICAgY29kZV9mb2xkaW5nOiBoaWRlCi0tLQoKYGBge3Igc2V0dXAsIG1lc3NhZ2U9RkFMU0V9CnJlcXVpcmUodGlkeXZlcnNlKQpyZXF1aXJlKGZsb3dDb3JlKQpyZXF1aXJlKGZsb3dDbHVzdCkKcmVxdWlyZShvcGVuQ3l0bykKcmVxdWlyZShnZ2N5dG8pCnJlcXVpcmUoY293cGxvdCkKYGBgCgojIEFuYWx5c2lzCgojIyBJbXBvcnQgZGF0YSBhbmQgZWRpdCB0aGUgbWV0YSBkYXRhCgppbXBvcnQgdGhlIGRhdGEgZnJvbSB0aGUgUkRTUyAoanVzdCBvbmNlKSBhbmQgdGhlbiB3cml0ZSBpdCB0byB0aGUgbG9jYWwgZGlzawpgYGB7ciBldmFsPUZBTFNFLCBpbmNsdWRlPUZBTFNFfQpkYXRhLnBhdGggPSAiL1ZvbHVtZXMvcmRzc19iaGUyL1Byb2plY3QvRTAxMy1QaG80cC1ldm9sdXRpb24vZmxvdy1jeXRvbWV0cnkvZGF0YS8yMDIyMDgwMy1FTy1yZWdpb24tNC4yLXJlcGVhdC8iCmZzIDwtIHJlYWQuZmxvd1NldChwYXRoID0gZGF0YS5wYXRoLCBwYXR0ZXJuID0gIkdyb3VwIiwgIyBleGNsdWRlIHRoZSBtYW51YWwgd2VsbHMKICAgICAgICAgICAgICAgICAgIHRyYW5zZm9ybWF0aW9uID0gRkFMU0UsICAjIHRoZSBvcmlnaW5hbCB2YWx1ZXMgYXJlIGFscmVhZHkgbGluZWFyaXplZC4gCiAgICAgICAgICAgICAgICAgICBlbXB0eVZhbHVlID0gRkFMU0UsICBhbHRlci5uYW1lcyA9IFRSVUUsICAgIyBjaGFuZ2UgcGFyYW1ldGVyIG5hbWVzIHRvIFIgZm9ybWF0CiAgICAgICAgICAgICAgICAgICBjb2x1bW4ucGF0dGVybiA9ICIuSHxGU0N8U1NDIikgIyBvbmx5IGxvYWQgdGhlIGhlaWdodCB2YXJpYWJsZXMgZm9yIHRoZSBmbHVvcmVzY2VudCBwYXJhbWV0ZXJzCm9yaU5hbWVzIDwtIHNhbXBsZU5hbWVzKGZzKQpgYGAKClNpbXBsaWZ5IHRoZSBzYW1wbGUgbmFtZXMKCmBgYHtyfQojIGltcG9ydCB0aGUgbG9uZ2VzdCBzdWJzdHJpbmcgZnVuY3Rpb24gZnJvbSB0aGUgUFRYUUMgcGFja2FnZSAoaHR0cHM6Ly9yZHJyLmlvL2NyYW4vUFRYUUMvbWFuL0xDU24uaHRtbCkKc291cmNlKCIuLi8uLi9zY3JpcHQvMjAyMjAzMjYtc2ltcGxpZnktbmFtZXMtc3Vicm91dGluZS5SIikKc2hvcnROYW1lcyA8LSBzaW1wbGlmeU5hbWVzKG9yaU5hbWVzKSAlPiUgCiAgZ3N1YigiLmZjcyIsIiIsLikKc2FtcGxlTmFtZXMoZnMpIDwtIHNob3J0TmFtZXMKYGBgCgpNZXRhZGF0YQoKYGBge3J9CnNhbXBsZSA8LSByZWFkLmNzdigiMjAyMjA4MDMtc2FtcGxlLWxpc3QuY3N2IikgJT4lIAogIGNvbHVtbl90b19yb3duYW1lcyh2YXIgPSAiZmlsZSIpCnBEYXRhKGZzKSA8LSBzYW1wbGUKYGBgCgojIyBHYXRpbmcKCk5leHQgd2UgdXNlIGEgc2VyaWVzIG9mIHBsb3RzIHRvIGd1aWRlIG91ciBnYXRpbmcgc3RyYXRlZ3kgZm9yIGlkZW50aWZ5aW5nIHRoZSBwb3B1bGF0aW9uIHdlIHdhbnQgdG8gd29yayB3aXRoLiBcIyMjIFJlbW92ZSBvdXRsaWVycyBXZSBmaXJzdCBnYXRlIG9uIEZTQy5IIGFuZCBTU0MuSCB0byByZW1vdmUgb3V0bGllcnMgKGV2ZW50cyB0aGF0IGFyZSB0b28gYmlnIG9yIHRvbyBzbWFsbCkuIFRoZSBBdHR1bmUgaW5zdHJ1bWVudCB3ZSB1c2UgY2FuIHJlY29yZCBzaXggZGVjYWRlcyAoMTBeMC0xMF42KSwgd2l0aCB0aGUgZmlyc3QgdHdvIGRlY2FkZXMgbW9zdGx5IG9jY3VwaWVkIGJ5IGVsZWN0cm9uaWMgbm9pc2UuCgpMZXQncyBmaXJzdCBkZWZpbmUgYSBnYXRlIGFuZCB2aXN1YWxpemUgaXQgaW4gYSBwbG90IGJlZm9yZSBhZGRpbmcgaXQgdG8gYSBHYXRpbmdTZXQuCgpgYGB7ciBmaWcud2lkdGg9MTIsIGZpZy5oZWlnaHQ9MTJ9Cm91dGxpZXIuZ2F0ZSA8LSByZWN0YW5nbGVHYXRlKGZpbHRlcklkID0gIi1vdXRsaWVyIiwgIkZTQy5IIiA9IGMoMS4yZTUsIDFlNiksICJTU0MuSCIgPSBjKDFlMiwgMWU2KSkKZ2djeXRvKGZzLCBhZXMoeCA9IEZTQy5ILCB5ID0gU1NDLkgpLCBzdWJzZXQgPSAicm9vdCIpICsKICBnZW9tX2hleChiaW5zID0gNjQpICsgZ2VvbV9nYXRlKG91dGxpZXIuZ2F0ZSkgKyBmYWNldF93cmFwKH5uYW1lLCBuY29sID0gOSkgKyB0aGVtZV9idygpCmBgYAoKQWRkIHRoaXMgZ2F0ZSB0byB0aGUgR2F0aW5nU2V0CgpgYGB7cn0KZ3MgPC0gR2F0aW5nU2V0KGZzKSAjIGNyZWF0ZSBhIEdhdGluZ1NldApnc19wb3BfYWRkKGdzLCBvdXRsaWVyLmdhdGUsIHBhcmVudCA9ICJyb290IikKcmVjb21wdXRlKGdzKQpgYGAKCiMjIyBQSE81LW1DaGVycnkgaW5kdWN0aW9uCgpXaGVuIEkgcGxvdHRlZCB0aGUgc2luZ2xldCBldmVudHMgb24gR0ZQLVJGUCAyZCBzcGFjZSwgSSBub3RpY2VkIGEgZmV3IHNhbXBsZXMgdGhhdCBzaG93IG1vcmUgdGhhbiBvbmUgcG9wdWxhdGlvbiBvZiBjZWxscywgd2hlcmUgdGhlIG1haW4gcG9wdWxhdGlvbiBhcHBlYXJlZCB0byBiZSAiaW5kdWNlZCIgd2hpbGUgb25lIG9yIG1vcmUgc3VicG9wdWxhdGlvbnMgYXJlIGxlc3Mgb3Igbm90IGluZHVjZWQuIFdoaWxlIHRoZSBiaW9sb2dpY2FsIHJlYXNvbnMgYmVoaW5kIHJlcXVpcmUgZnVydGhlciBpbnZlc3RpZ2F0aW9uIGFuZCBtYXkgYmUgdmVyeSBpbnRlcmVzdGluZyAoaGV0ZXJvZ2VuZWl0eSksIGZvciB0aGlzIGFuYWx5c2lzIHdlIHdpbGwgdXNlIGZsb3dDbHVzdCB0byBpZGVudGlmeSB0aGUgbWFpbiBwb3B1bGF0aW9uIGFuZCBtb3ZlIGZvcndhcmQuCgpgYGB7ciBmaWcuaGVpZ2h0PTEyLGZpZy53aWR0aD0xMn0KZ2djeXRvKGdzLCBhZXMoeCA9IEJMMS5ILCB5ID0gWUwyLkgpLCBzdWJzZXQgPSAiLW91dGxpZXIiKSArIGdlb21faGV4KGJpbnMgPSA2NCkgKwogIGZhY2V0X3dyYXAofm5hbWUsIG5jb2wgPSA5KSArIHNjYWxlX3hfbG9naWNsZSgpICsgc2NhbGVfeV9sb2dpY2xlKCkgKyB0aGVtZV9idygpCmBgYAoKPiBCZSBjYXJlZnVsIHdoZW4gd29ya2luZyB3aXRoIHRoZSBHYXRpbmdTZXQgYW5kIEdhdGluZ0hpZXJhcmNoeSBvYmplY3RzIC0tIHRoZXNlIGFyZSBzdHJpY3RseSByZWZlcmVuY2UgY2xhc3NlcywgbWVhbmluZyB0aGF0IG1vc3Qgb2YgdGhlIG9wZXJhdGlvbnMgd29yayBieSBwb2ludGVycyBhbmQgdGhlIG9wZXJhdGlvbnMgd2lsbCBjaGFuZ2UgdGhlIHVuZGVybHlpbmcgZGF0YS4gRm9yIGV4YW1wbGUsIHRoZSBmaXJzdCBsaW5lIG9mIHRoZSBjb2RlIGJlbG93IChjb21tZW50ZWQgb3V0KSBvYnRhaW5zIGEgcG9pbnRlciB0byB0aGUgdW5kZXJseWluZyBkYXRhIHJhdGhlciB0aGFuIG1ha2luZyBhIGNvcHkgb2YgdGhhdCBkYXRhLiBhbnkgb3BlcmF0aW9ucyBvbiBpdCB3aWxsIGNoYW5nZSB0aGUgb3JpZ2luYWwgZGF0YSBhcyBhIHJlc3VsdC4KCmBgYHtyfQojZXggPC0gZ3NfcG9wX2dldF9kYXRhKGdzLCAic2luZ2xldCIpW1sxXV0KZXggPC0gZnNbWyJOQS0xNTYtMiJdXQojbGdjbCA8LSBlc3RpbWF0ZUxvZ2ljbGUoZXgsIGNoYW5uZWxzID0gYygiQkwxLkgiLCAiWUwyLkgiKSkKbGdjbCA8LSBsb2dpY2xlVHJhbnNmb3JtKCJpbmR1Y3Rpb24iKQojIHNldCBjbHVzdGVyIGdhdGUgcGFyYW1ldGVycwprID0gMTsgcSA9IDAuOQojIGVuZCBzZXR0aW5nCmV4IDwtIHRyYW5zZm9ybShleCwgbGdCTDEuSCA9IGxnY2woYEJMMS5IYCksIGxnWUwyLkggPSBsZ2NsKGBZTDIuSGApKQpmbHVvLmdhdGUgPC0gZ2F0ZV9mbG93Y2x1c3RfMmQoZXgsICJsZ0JMMS5IIiwgImxnWUwyLkgiLCBLID0gaywgcXVhbnRpbGUgPSBxKQpnZ2N5dG8oZXgsIGFlcyh4ID0gbGdCTDEuSCwgeSA9IGxnWUwyLkgpKSArIGdlb21faGV4KGJpbnMgPSA2NCkgKyBnZW9tX2dhdGUoZmx1by5nYXRlKSArIGdlb21fc3RhdHMoKSMgKyBzY2FsZV94X2xvZ2ljbGUoKSArIHNjYWxlX3lfbG9naWNsZSgpCmBgYAoKRXZlbiB0aG91Z2ggZmxvd0NsdXN0IGlzIHN1cHBvc2VkIHRvIHBlcmZvcm0gaXRzIG93biB0cmFuc2Zvcm1hdGlvbiAobW9kaWZpZWQgQm94LUNveCksIGVtcGlyaWNhbGx5IEkgZm91bmQgdGhlIGNsdXN0ZXJpbmcgc2VlbSB0byB3b3JrIGJldHRlciBvbiBsb2dpY2xlIHRyYW5zZm9ybWVkIGRhdGEgZm9yIHRoZSB0d28gZmx1b3Jlc2NlbnQgY2hhbm5lbHMuIFRoZXJlZm9yZSBJJ20gdHJhbnNmb3JtaW5nIHRoZSB1bmRlcmx5aW5nIGRhdGEgb2YgdGhlIEdhdGluZ1NldC4gTm90ZSB0aGF0IGl0IHNlZW1zIHRvIGJlIGRpZmZpY3VsdCB0byAiY3JlYXRlIG5ldyBwYXJhbWV0ZXJzIiB0byBzdG9yZSB0aGUgdHJhbnNmb3JtZWQgZGF0YSwgd2hpbGUga2VlcGluZyB0aGUgb3JpZ2luYWwgZGF0YSBpbnRhY3QuIEluc3RlYWQsIHRoZSB0cmFuc2Zvcm1hdGlvbiBmdW5jdGlvbnMgY29uc3RydWN0ZWQgdXNpbmcgdGhlIGNvbnN0cnVjdG9yIGBsb2dpY2xlX3RyYW5zKClgIHN0b3JlcyB0aGUgaW52ZXJzZSB0cmFuc2Zvcm1hdGlvbiBmdW5jdGlvbnMsIHdoaWNoIGNhbiBiZSB1c2VkIHRvIHBlcmZvcm0gdGhlIGludmVyc2UgdHJhbnNmb3JtYXRpb24gd2hlbiBuZWVkZWQuIEZvbGxvd2VkIHRoZSBtYW51YWwgZm9yIEdhdGluZ1NldCBbaGVyZV0oaHR0cHM6Ly93d3cuYmlvY29uZHVjdG9yLm9yZy9wYWNrYWdlcy9kZXZlbC9iaW9jL3ZpZ25ldHRlcy9mbG93V29ya3NwYWNlL2luc3QvZG9jL2Zsb3dXb3Jrc3BhY2UtSW50cm9kdWN0aW9uLmh0bWwjMDNfR2F0aW5nSGllcmFyY2h5KQoKYGBge3J9CmxnY2wgPC0gbG9naWNsZV90cmFucygpCnRyYW5zTGlzdCA8LSB0cmFuc2Zvcm1lckxpc3QoYyhsZ0JMMS5IID0gIkJMMS5IIiwgbGdjbFlMMi5IID0gIllMMi5IIiksIGxnY2wpCnRyYW5zZm9ybShncywgdHJhbnNMaXN0KQpgYGAKCj4gdG8gb2J0YWluIHRoZSBvcmlnaW5hbCBkYXRhLCB1c2UgYGdzX3BvcF9nZXRfZGF0YShnc1tbMV1dLCBpbnZlcnNlLnRyYW5zZm9ybSA9IFRSVUUpYAoKTm93IHdlIGNhbiBkbyB0aGUgZmxvd0NsdXN0IGdhdGluZwoKYGBge3IgbWVzc2FnZT1GQUxTRX0KZGF0IDwtIGdzX3BvcF9nZXRfZGF0YShncywgIi1vdXRsaWVyIikgIyBnZXQgcGFyZW50IGRhdGEKaW5kdWN0aW9uR2F0ZSA8LSBmc0FwcGx5KGRhdCwgZnVuY3Rpb24oZnIpCiAgb3BlbkN5dG86OmdhdGVfZmxvd2NsdXN0XzJkKGZyLCAiQkwxLkgiLCAiWUwyLkgiLCBLID0gaywgcXVhbnRpbGUgPSBxKQopCmdzX3BvcF9hZGQoZ3MsIGluZHVjdGlvbkdhdGUsIHBhcmVudCA9ICItb3V0bGllciIsIG5hbWUgPSAiaW5kdWN0aW9uIikKcmVjb21wdXRlKGdzKQpgYGAKCmBgYHtyLCBmaWcud2lkdGg9MTIsIGZpZy5oZWlnaHQ9MTJ9CmdnY3l0byhncywgYWVzKHggPSBCTDEuSCwgeSA9IFlMMi5IKSwgc3Vic2V0ID0gIi1vdXRsaWVyIikgKyBnZW9tX2hleChiaW5zID0gNjQpICsgZ2VvbV9nYXRlKCJpbmR1Y3Rpb24iKSArIGdlb21fc3RhdHMoKSArIAogIGZhY2V0X3dyYXAofm5hbWUsIG5jb2wgPSA5KSArIHRoZW1lX2J3KCkKYGBgCgpTZXZlcmFsIHNhbXBsZXMgZGlkbid0IHdvcmsgd2VsbCB3aXRoIHRoZSBjbHVzdGVyaW5nIGFsZ29yaXRobS4gSGVyZSB3ZSB3aWxsIHVzZSBhIDIgY2x1c3RlciBnYXRpbmcgc3RyYXRlZ3kgdG8gc2VsZWN0IHRoZSBleHByZXNzaW5nIHBvcHVsYXRpb24uCmBgYHtyLCBtZXNzYWdlPUZBTFNFfQpsaXN0LnJlZG8gPC0gYygiMTg4LTU1NS0zIiwgIjE4OC01NTUtNiIsICIyNDEtNTU1LTEiKQpuZXdHYXRlIDwtIGxhcHBseShsaXN0LnJlZG8sIGZ1bmN0aW9uKHgpewogIGdhdGVfZmxvd2NsdXN0XzJkKGRhdFtbeF1dLCAiQkwxLkgiLCAiWUwyLkgiLCBLID0gMiwgcXVhbnRpbGUgPSAwLjksIHRhcmdldCA9IGMoMyw0KSkKfSkKbmFtZXMobmV3R2F0ZSkgPC0gbGlzdC5yZWRvCmdnY3l0byhnc1tsaXN0LnJlZG9dLCBhZXMoeCA9IEJMMS5ILCB5ID0gWUwyLkgpLCBzdWJzZXQgPSAiLW91dGxpZXIiKSArIGdlb21faGV4KGJpbnMgPSA2NCkgKwogIGdlb21fZ2F0ZShuZXdHYXRlKSArIHRoZW1lX2J3KCkgIysgZ2VvbV9zdGF0cygpCmBgYAoKdXBkYXRlIHRoZSBpbmR1Y3Rpb25HYXRlIG9iamVjdApgYGB7cn0KZ3NfcG9wX3NldF9nYXRlKGdzW2xpc3QucmVkb10sICJpbmR1Y3Rpb24iLCBuZXdHYXRlKQpyZWNvbXB1dGUoZ3NbbGlzdC5yZWRvXSwgImluZHVjdGlvbiIpCmBgYAoKQ2hlY2sgdGhlIHJlc3VsdHMKYGBge3IgZmlnLndpZHRoPTEyLCBmaWcuaGVpZ2h0PTEyfQpnZ2N5dG8oZ3MsIGFlcyh4ID0gQkwxLkgsIHkgPSBZTDIuSCksIHN1YnNldCA9ICItb3V0bGllciIpICsKICBnZW9tX2hleChiaW5zID0gNjQpICsgZ2VvbV9nYXRlKCJpbmR1Y3Rpb24iKSArIGdlb21fc3RhdHMoKSArCiAgZmFjZXRfd3JhcCh+bmFtZSwgbmNvbCA9IDkpICsgdGhlbWVfYncoKQpgYGAKCiMjIE5vcm1hbGl6YXRpb24KCkJyaWFuIE1ldHpnZXIgYW5kIGNvbGxlYWd1ZXMgcHJvcG9zZWQgYSBzaW1wbGUgY29ycmVjdGlvbiBpbiB0aGVpciAyMDE1IHBhcGVyLiBUaGUgaW50dWl0aW9uIGJlaGluZCB0aGlzIG1ldGhvZCBpcyB0aGF0IEZTQyBpcyBwcm9wb3J0aW9uYWwgdG8gdGhlIG1heCAyZCBwcm9qZWN0aW9uIChhcmVhKSBvZiBhIGNlbGwsIGFuZCB0aHVzIEZTQ1xeKDMvMikgc2hvdWxkIGJlIHJvdWdobHkgcHJvcG9ydGlvbmFsIHRvIHRoZSB2b2x1bWUuIEJ5IGNvbnRyYXN0LCB0aGUgZmx1b3Jlc2NlbnQgY2hhbm5lbHMgc2hvdWxkIGJlIHRoZSBjdW11bGF0aXZlIGludGVuc2l0eSBmcm9tIHRoZSB3aG9sZSBjZWxsLiBUaGVyZWZvcmUgZGl2aWRpbmcgRlAgaW50ZW5zaXR5IGJ5IEZTQ1xeKDMvMikgc2hvdWxkIGVmZmVjdGl2ZWx5IHJlbW92ZSB0aGUgdmFyaWF0aW9uIGR1ZSB0byBjZWxsIHNpemUgZGlmZmVyZW5jZS4gSGUgZGlkIHNheSB0aGF0IHRoZSBXaXR0a29wcCBsYWIgbGF0ZXIgc3dpdGNoZWQgdG8gYSBkaWZmZXJlbnQgbWV0aG9kIGJhc2VkIG9uIFBDQSBhbmQgcm90YXRpb25zLiBBZnRlciByZWFkaW5nIHRob3NlIHBhcGVycywgSSB0aG91Z2h0IHRoZSBzaW1wbGVyIG9uZSB3aWxsIHN1ZmZpY2UgZm9yIHVzLgoKVGVzdCBub3JtYWxpemF0aW9uIGZvcm11bGEKCmBgYHtyfQojIGdldCBwb3B1bGF0aW9uIGRhdGEKZnMub3V0IDwtIGdzX3BvcF9nZXRfZGF0YShncywgeSA9ICJpbmR1Y3Rpb24iLCBpbnZlcnNlLnRyYW5zZm9ybSA9IFRSVUUpICMgZ2V0IHRoZSBpbnZlcnNlIHRyYW5zZm9ybWVkIGRhdGEKIyBjb21lIHVwIHdpdGggYW4gYXBwcm94aW1hdGUgRlNDLkggdmFsdWUgZm9yIGFuIGF2ZXJhZ2UgZXZlbnQgdG8gYmUgdXNlZCBhIHNjYWxhciBmb3IgdGhlIG5leHQgc3RlcAptZnNjIDwtIDVlNSAjIGJhc2VkIG9uIHRoZSBtb2RlIG9mIHRoZSBtZWRpYW4gb2YgRlNDLkggZnJvbSBhbGwgc2FtcGxlcwojIGZzLm91dCBpcyBvZiB0aGUgY3l0b2ZyYW1lIGNsYXNzLCB3aGljaCBpcyBhIHJlZmVyZW5jZSBjbGFzcy4gbmVlZCB0byBjb252ZXJ0IHRvIGZsb3dmcmFtZSBmb3IgdHJhbnNmb3JtYXRpb24KIyBodHRwczovL3d3dy5iaW9jb25kdWN0b3Iub3JnL3BhY2thZ2VzL2RldmVsL2Jpb2MvdmlnbmV0dGVzL2Zsb3dXb3Jrc3BhY2UvaW5zdC9kb2MvZmxvd1dvcmtzcGFjZS1JbnRyb2R1Y3Rpb24uaHRtbApleHBvbmVudCA8LSAxLjUKIyBleCA8LSBjeXRvZnJhbWVfdG9fZmxvd0ZyYW1lKGZzLm91dFtbIkE5Il1dKSAlPiUKIyAgIHRyYW5zZm9ybShuRlNDID0gRlNDLkgvbWZzYykgJT4lIAojICAgdHJhbnNmb3JtKG5HRlAgPSBCTDEuSC9uRlNDXihleHBvbmVudCksIG5SRlAgPSBZTDIuSC9uRlNDXihleHBvbmVudCkpCgpleCA8LSBjeXRvc2V0X3RvX2Zsb3dTZXQoZnMub3V0KSAlPiUgCiAgdHJhbnNmb3JtKG5GU0MgPSBGU0MuSC9tZnNjKSAlPiUgCiAgdHJhbnNmb3JtKG5HRlAgPSBCTDEuSC9uRlNDXihleHBvbmVudCksIG5SRlAgPSBZTDIuSC9uRlNDXihleHBvbmVudCkpCmBgYAoKCnBsb3QgdGhlIGV4YW1wbGVzCmBgYHtyIGZpZy53aWR0aD0xMiwgZmlnLmhlaWdodD0xMn0KZ2dwbG90KGV4LCBhZXMoeCA9IG5GU0MsIHkgPSBuUkZQLzFlMykpICsgZ2VvbV9oZXgoYmlucyA9IDY0KSArIHNjYWxlX3lfbG9nMTAoKSArIHN0YXRfc21vb3RoKG1ldGhvZCA9ICJsbSIpICsKICBzY2FsZV9maWxsX2dyYWRpZW50bihjb2xvdXJzID0gcmV2KFJDb2xvckJyZXdlcjo6YnJld2VyLnBhbCgxMSwgIlNwZWN0cmFsIikpKSArIGZhY2V0X3dyYXAofm5hbWUsIHNjYWxlcyA9ICJmcmVlX3kiKSArCiAgdGhlbWVfYncoKQpgYGAKCmBgYHtyfQojIGNhbGN1bGF0ZSByYXRpbwpub3JtLmRhdGEgPC0gZnNBcHBseShmcy5vdXQsIGZ1bmN0aW9uKGNmKSB7CiAgY2YgPC0gY2JpbmQoY2YsIAogICAgICAgICAgICAgIG5SRlAgPSBjZlssIllMMi5IIl0gLyAoY2ZbLCAiRlNDLkgiXS9tZnNjKV4oZXhwb25lbnQpLCAKICAgICAgICAgICAgICBuR0ZQID0gY2ZbLCJCTDEuSCJdIC8gKGNmWywgIkZTQy5IIl0vbWZzYyleKGV4cG9uZW50KSkKICBhcHBseShjZlssIGMoIkZTQy5IIiwgIkJMMS5IIiwgIllMMi5IIiwgIm5HRlAiLCAiblJGUCIpXSwgMiwgbWVkaWFuKQogIH0sIHVzZS5leHBycyA9IFRSVUUpICU+JSAKICBhc190aWJibGUocm93bmFtZXMgPSAibmFtZSIpICAlPiUgCiAgbXV0YXRlKGFjcm9zcyhCTDEuSDpuUkZQLCB+IHJvdW5kKC54LCAxKSkpCmBgYAoKIyBPdXRwdXQKClRoZSBnb2FsIGlzIHRvIGV4cG9ydCB0aGUgZ2F0ZWQgZXZlbnRzIGFuZCBjYWxjdWxhdGUgdGhlIFJGUC9HRlAgYW5kIHRha2UgdGhlIG1lZGlhbiwgd2hpY2ggd2lsbCBiZSB1c2VkIGluIGRvd25zdHJlYW0gYW5hbHlzZXMuCgpHZXQgdGhlIHBvcHVsYXRpb24gc3RhdHMKCmBgYHtyfQpzdGF0cyA8LSBnc19wb3BfZ2V0X3N0YXRzKGdzKSAlPiUgCiAgYXNfdGliYmxlKCkgJT4lIAogIG11dGF0ZShwb3AgPSBnc3ViKCIuKi8iLCAiIiwgcG9wKSwgcG9wID0gZ3N1YigiLW91dGxpZXIiLCAiY2VsbHMiLCBwb3ApKSAlPiUgCiAgcGl2b3Rfd2lkZXIobmFtZXNfZnJvbSA9IHBvcCwgbmFtZXNfcHJlZml4ID0gIm5fIiwgdmFsdWVzX2Zyb20gPSBjb3VudCkKYGBgCgpFeHBvcnQgdGhlIGRhdGEKCmBgYHtyfQojIHB1bGwgYWxsIGluZm8gdG9nZXRoZXIgaW4gYSBzaW5nbGUgdGliYmxlCmZpbmFsIDwtIGxlZnRfam9pbihhc190aWJibGUocERhdGEoZnMpKSwgc3RhdHMsIGJ5ID0gYygibmFtZSIgPSAic2FtcGxlIikpICU+JSAKICBsZWZ0X2pvaW4obm9ybS5kYXRhLCBieSA9ICJuYW1lIikKd3JpdGVfdHN2KGZpbmFsLCAiMjAyMjA4MDMtZ2F0ZWQtbWVkaWFuLW91dC50eHQiKQpgYGAK